package com.billow.net.rtmp
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.ProgressEvent;
	import flash.net.ObjectEncoding;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	
	[Event(name = "client_close", type = "com.billow.net.rtmp.ClientControlEvent")]
	[Event(name = "client_handShake", type = "com.billow.net.rtmp.ClientControlEvent")]
	[Event(name = "client_media", type = "com.billow.net.rtmp.ClientControlEvent")]
	[Event(name = "client_invokeName", type = "com.billow.net.rtmp.ClientControlEvent")]
	
	internal class ClientControl extends EventDispatcher
	{
		private var _decoder:RtmpDecoder;
		private var _encoder:RtmpEncoder;
		private var _socket:Socket;
		private var _info:ClientControlInfo;
		private var _packet:RtmpPacket;
		private var _bytelength:uint = 0;
		private var _handshakeIndex:uint = 0;
		
		public function ClientControl(socket:Socket ,id:String ,objectEncoding:uint)
		{
			_socket = socket;
			_socket.addEventListener(Event.CLOSE ,closeHandler);
			_socket.addEventListener(ProgressEvent.SOCKET_DATA ,receiveDataHandler);
			
			_encoder = new RtmpEncoder();
			_decoder = new RtmpDecoder();
			_decoder.addEventListener("decoder_complete" ,decoderComplete);
			
			_info = new ClientControlInfo();
			_info.id = id;
			_info.objectEncoding = objectEncoding;
			_info.remoteAddress = _socket.remoteAddress;
			_info.remotePort = _socket.remotePort;
		}
		
		
		
		
		///////////////////////////////////////////////////////////////////////////////
		//
		// Public
		//
		//////////////////////////////////////////////////////////////////////////////
		
		/**
		 * toString 
		 * @return 
		 * 
		 */		
		override public function toString():String
		{
			return "connected = " + _info.connected +
				", id = " + _info.id +
				", remoteAddress = " + _info.remoteAddress +
				", remotePort = " + _info.remotePort +
				", application = " + _info.application +
				", flashVersion = " + _info.flashVersion +
				", swfURL = " + _info.swfURL +
				", tcURL = " + _info.tcURL+
				", pageURL = " + _info.pageURL;
		}
		
		/** 数据  */	
		public function get info():ClientControlInfo {return _info;}
		
		/** 数据包 */	
		public function get packet():RtmpPacket {return _packet;}
		
		
		
		/**
		 * 关闭连接 
		 * 
		 */		
		public function close():void
		{
			_info.connected = false;
			_decoder.removeEventListener("decoder_complete" ,decoderComplete);
			
			if(_socket!=null)
			{
				_socket.removeEventListener(Event.CLOSE ,closeHandler);
				_socket.removeEventListener(ProgressEvent.SOCKET_DATA ,receiveDataHandler);
				if(_socket.connected)_socket.close();
				_socket = null;
			}
		}
		
		/**
		 * 发送数据 
		 * @param byte
		 * 
		 */		
		public function send(byte:ByteArray):void
		{
			if(_socket==null)return;
			_socket.writeBytes(byte);
			_socket.flush();
		}
		
		/**
		 * 发送数据包 
		 * @param packet
		 * 
		 */		
		public function sendPacket(packet:RtmpPacket):void
		{
			send(_encoder.process(packet));
		}
		
		
		
		
		
		
		
		
		///////////////////////////////////////////////////////////////////////////////
		//
		// Event
		//
		//////////////////////////////////////////////////////////////////////////////
		
		/**
		 * 离线 
		 * @param event
		 * 
		 */		
		private function closeHandler(event:Event):void
		{
			close();
			dispatchEvent(new ClientControlEvent(ClientControlEvent.CLIENT_CLOSE));
		}
		
		/**
		 * 数据处理 
		 * @param event
		 * 
		 */		
		private function receiveDataHandler(event:ProgressEvent):void
		{
			if(_info.connected==false)
			{
				//手机 握手 数据包可能不够长度，(手机会自动重新发包) 
				if(_socket.bytesAvailable<1536)return;
				_handshakeIndex==0?handshake_1():handshake_2();
				_handshakeIndex++;
			}else{
				try{
					_bytelength += _socket.bytesAvailable;
					_decoder.process(_socket, _info.objectEncoding);
				}catch(e:Error){
					close();
					trace("packet error:"+e.message);
				}
			}
		}
		
		/**
		 * 解码完成事件 
		 * @param event
		 * 
		 */		
		private function decoderComplete(event:Event):void
		{
			_packet = _decoder.packet;
			
			switch(_packet.rtmpBodyType)
			{
				case 0x03:				sendPacket(bytesReadMessage(_bytelength));										break;
				case 0x04: 				sendPacket(pingMessage(0, 0, 0, 0)); 											break;
				case 0x05:				sendPacket(bindInMessage(0));													break;
				case 0x06:				sendPacket(bindOutMessage(0));													break;
				case 0x08: case 0x09: 	dispatchEvent(new ClientControlEvent(ClientControlEvent.CLIENT_MEDIA));			break;
				case 0x11: case 0x14: 	recvDataPakcet(); 																break;
				case 0x16:				trace("类型 = 0x16 流数据!");													break;
				default:				trace("其它类型包! 类型(type) = " + _packet.rtmpBodyType);					break;
			}
			
			if(_packet!=null)
			{
				_packet.clear();
				_packet = null;
			}
		}
		
		
		///////////////////////////////////////////////////////////////////////////////
		//
		// Handler
		//
		//////////////////////////////////////////////////////////////////////////////
		
		/** 握手步骤 1 */
		private function handshake_1():void
		{
			try{
				var result:ByteArray = new ByteArray();
				var tmp:ByteArray = new ByteArray();
				_socket.readByte();
				_socket.readBytes(tmp, 0, 1536);
				result.writeByte(0x03);
				result.writeBytes(tmp);
				result.writeBytes(tmp);
				result.position = 0;
				
				send(result);
			}catch(e:Error){
				close();
				trace("握手步骤 1 失败!");
			}
		}
		
		/** 握手步骤 2 */
		private function handshake_2():void
		{
			try{
				var result:ByteArray = new ByteArray();
				_socket.readBytes(result, 0, 1536);
				result.clear();
				_socket.readBytes(result);
				result.position = 0;
				_decoder.process(result ,ObjectEncoding.AMF0);
			}catch(e:Error){
				close();
				trace("握手步骤 2 失败!");
			}
		}
		
		/** bytesReadMessage */
		private function bytesReadMessage(bytelength:uint):RtmpPacket
		{
			var packet:RtmpPacket = new RtmpPacket();
			packet.rtmpChannel = 0x02;
			packet.rtmpBodyType = 0x03;
			packet.streamChannel = 0;
			packet.rtmpBody.writeUnsignedInt(bytelength);
			packet.rtmpBodySize = 4;
			return packet;
		}
		
		/** pingMessage */
		private function pingMessage(p1:int, p2:int, p3:int = -1, p4:int = -1):RtmpPacket
		{
			var packet:RtmpPacket = new RtmpPacket();
			packet.rtmpBody.writeShort(p1);
			packet.rtmpBody.writeUnsignedInt(p2);
			if(p3 != -1) packet.rtmpBody.writeUnsignedInt(p3);
			if(p4 != -1) packet.rtmpBody.writeUnsignedInt(p4);
			packet.rtmpChannel = 0x02;
			packet.rtmpBodyType = 0x04;
			packet.rtmpBodySize = packet.rtmpBody.length;
			return packet;
		}
		
		/** bindInMessage */
		private function bindInMessage(bandIn:uint):RtmpPacket
		{
			var packet:RtmpPacket = new RtmpPacket();
			packet.rtmpChannel = 0x02;
			packet.rtmpBodyType = 0x05;
			packet.streamChannel = 0;
			packet.rtmpBody.writeUnsignedInt(bandIn);
			packet.rtmpBodySize = 4;
			return packet;
		}
		
		/** bindOutMessage */
		private function bindOutMessage(bandOut:uint):RtmpPacket
		{
			var packet:RtmpPacket = new RtmpPacket();
			packet.rtmpChannel = 0x02;
			packet.rtmpBodyType = 0x06;
			packet.streamChannel = 0;
			packet.rtmpBody.writeUnsignedInt(bandOut);
			packet.rtmpBodySize = 4;
			return packet;
		}
		
		/** recvDataPakcet */
		private function recvDataPakcet():void
		{
			if(_info.objectEncoding == ObjectEncoding.AMF3)_packet.rtmpBody.readByte();
			
			_info.invokeName = _packet.readObject() as String;
			
			trace("服务器消息(server)："+_info.id+" 通知名：" + '"'+_info.invokeName+'"');
			
			if(_info.invokeName=="connect")
			{
				var responseID:uint = _packet.readObject() as uint;
				var connectInfo:Object = _packet.readObject();
				
				_info.connected = true;
				_info.objectEncoding = connectInfo["objectEncoding"];
				_info.application = connectInfo["app"];
				_info.flashVersion = connectInfo["flashVer"];
				_info.swfURL = connectInfo["swfUrl"];
				_info.tcURL = connectInfo["tcUrl"];
				_info.pageURL = connectInfo["pageUrl"];
				
				dispatchEvent(new ClientControlEvent(ClientControlEvent.CLIENT_HANDSHAKE));
			}else{
				dispatchEvent(new ClientControlEvent(ClientControlEvent.CLIENT_INVOKENAME));
			}
		}
	}
}